package mycoffee.order.decoder;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import mycoffee.order.model.Constants;
import mycoffee.order.model.Food;
import mycoffee.order.utils.LogUtils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;

/**
 * This class is used to decode bitmap for list.
 * 
 */
public class CacheManager {
	/** Used to communicate with UI. */
	private DecoderListener mCommunicator;
	/** Array informations of image which want to decode. */
	private ArrayList<Food> mItems;
	/** To notice that decoding is finished. */
	private boolean mIsFinish;
	/** The flag to stop the decoding. */
	private boolean mIsStop = true;
	/** The flag to pause the decoding. */
	private boolean mIsPausing = false;
	/** The first position that is displaying on UI. */
	private int mFirstVisiblePos;
	/** The last position that is displaying on UI. */
	private int mLastVisiblePos;
	/** the max size to decode bitmap. */
	private int mResolutionDecode = Constants.INVALID_VALUE;
	/** Interface to global information about an application environment. */
	private Context mContext;
	/** The initial capacity for hard cache. */
	private int mCacheCapacity = 50;
	/** Hard cache is only clear when screen is destroy. */
	private HashMap<String, Bitmap> mHardBitmapCache = null;
	/** Soft cache will be able clean by GC. */
	private ConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache = null;

	private static final String TAG = "Decoder";

	/**
	 * 
	 */
	public CacheManager(Context context, ArrayList<Food> items,
			int cacheCapacity, DecoderListener listener) {
		mContext = context;
		mItems = items;
		mCacheCapacity = cacheCapacity;
		mCommunicator = listener;
		mHardBitmapCache = new LinkedHashMap<String, Bitmap>(
				mCacheCapacity / 2, 0.75f, true);
		mSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
				mCacheCapacity / 2);
	}

	public void setMaxSize(int maxSize) {
		setResolutionDecode(maxSize);
	}

	/**
	 * @param isPause
	 *            True: want to pause decoding.
	 */
	public void setPause(boolean isPause) {
		mIsPausing = isPause;
	}

	/**
	 * Get the status to know whether decoding processing is finished or not.
	 * 
	 * @return the status of decoding processing. It means that finish decoding
	 *         finish if it is true.
	 */
	public final boolean isFinish() {
		return mIsFinish;
	}

	/**
	 * This function is used to set value for stopping this thread.
	 * 
	 * @param isStop
	 *            set true if want to stop this thread
	 */
	public final void setStop(final boolean isStop) {
		mIsStop = isStop;
	}

	/**
	 * Get the status to know whether the decoding processing is stop or not.
	 * 
	 * @return return true if the decoding processing is stop
	 */
	public final boolean isStop() {
		return mIsStop;
	}

	/**
	 * Get the status to know whether the decoding processing is pause or not.
	 * 
	 * @return return true if the decoding processing is pause
	 */
	public final boolean isPause() {
		return mIsPausing;
	}

	/**
	 * Set the position of item which will be decode from here.
	 * 
	 * @param pos
	 *            the position wants to set
	 */
	public final void setFirstVisiblePos(final int pos) {
		mFirstVisiblePos = pos;
	}

	/**
	 * Set the position of item which will be decode to here.
	 * 
	 * @param pos
	 *            the position wants to set
	 */
	public final void setLastVisiblePos(final int pos) {
		mLastVisiblePos = pos;
	}

	/**
	 * Set to decode from specified first position to specified last position.
	 * 
	 * @param first
	 *            the first position
	 * @param last
	 *            the last position
	 */
	public final void setVisiblePos(final int first, final int last) {
		mFirstVisiblePos = first;
		mLastVisiblePos = last;
	}

	public Bitmap getBitmap(String key) {
		return mHardBitmapCache.get(key);
	}

	/**
	 * start decode image.
	 */
	public void execute() {
		// don't create thread to decode if it exist.
		if (!mIsStop) {
			return;
		}
		mIsStop = false;
		if (mHardBitmapCache == null) {
			return;
		}
		AsyncTask<Void, Integer, Boolean> asyncTask = new AsyncTask<Void, Integer, Boolean>() {

			@Override
			protected Boolean doInBackground(Void[] arg0) {
				int pos = findPositionToDecode();
				if (pos < 0) {
					mIsPausing = true;
				}
				while (!mIsStop) {
					if (!mIsPausing) {

						try {
							if (pos >= 0 && createBitmap(pos) && !mIsStop) {
								onProgressUpdate(pos);
							}
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						pos = findPositionToDecode();
						if (pos < 0) {
							mIsPausing = true;
						}
					}
				}
				// LogUtils.logError(TAG,
				// "[execute]-Finish Thread used to decode.");
				return true;
			}

			@Override
			protected void onProgressUpdate(Integer... values) {
				mCommunicator.onFinishDecode(values);
				super.onProgressUpdate(values);
			}
		};
		asyncTask.execute();
	}

	/** Used to find the position to continue decode. */
	protected final int findPositionToDecode() {
		int pos = Constants.INVALID_VALUE;

		if (mItems == null) {
			return pos;
		}
		synchronized (mItems) {
			int numberItems = mItems.size();
			LogUtils.logError(TAG, "findPositionToDecode: numberItems = "
					+ numberItems);

			String filePath = null;
			for (int i = mFirstVisiblePos; i <= mLastVisiblePos
					&& i < numberItems; i++) {
				try {
					filePath = mItems.get(i).getImage();
				} catch (NullPointerException e) {
					// LogUtils.logError(TAG, "The data is clean.");
				}
				if (TextUtils.isEmpty(filePath)) {
					continue;
				}
				// check on mHardBitmapCache
				if (mHardBitmapCache != null
						&& !mHardBitmapCache.containsKey(filePath)) {
					pos = i;
					break;
				}
			}

			// Continue decode in extra area
			if (pos == Constants.INVALID_VALUE && mHardBitmapCache != null
					&& mHardBitmapCache.size() < mCacheCapacity) {

				int maxDestance = Math.max(mFirstVisiblePos, numberItems
						- mLastVisiblePos);
				for (int i = 1; i <= maxDestance; i++) {

					if (i + mLastVisiblePos < numberItems
							|| mFirstVisiblePos - i >= 0) {
						if (i + mLastVisiblePos < numberItems
								&& i + mLastVisiblePos < numberItems) {
							// check first.
							filePath = mItems.get(i + mLastVisiblePos)
									.getImage();
							if (mHardBitmapCache != null
									&& !mHardBitmapCache.containsKey(filePath)) {
								pos = i + mLastVisiblePos;
								break;
							}
						} else if (mFirstVisiblePos - i >= 0
								&& mFirstVisiblePos - i < numberItems) {
							// check last
							filePath = mItems.get(mFirstVisiblePos - i)
									.getImage();
							if (mHardBitmapCache != null
									&& !mHardBitmapCache.containsKey(filePath)) {
								pos = mFirstVisiblePos - i;
								break;
							}
						}
					} else {
						break;
					}
				}
			}
		}

		return pos;
	}

	/**
	 * Decode an image to bitmap and cache it.
	 * 
	 * @return true if the bitmap has been decoded. Otherwise return false.
	 */
	private boolean createBitmap(int pos) throws IOException {
		if (mHardBitmapCache == null || mItems == null) {
			return false;
		}
		String path = null;
		synchronized (mItems) {
			if (mItems == null || pos < 0 || mItems.size() <= pos) {
				return false;
			}

			// LogUtils.logDebug(TAG + " [createBitmap()]", "position: " + pos);

			path = mItems.get(pos).getImage();
		}

		Bitmap bitmap = null;
		// mMaxSize > 0: decode thumb.
		// else: decode original image.
		try {
			if (mHardBitmapCache == null) {
				return false;
			}
			synchronized (mHardBitmapCache) {
				// check size of mHardBitmapCache
				// if size of it > mCacheCapacity --> move one bitmap to
				// softCache.
				if (mHardBitmapCache == null) {
					return false;
				}
				if (mHardBitmapCache.size() >= mCacheCapacity) {
					if (!removeABitmapFromCache()) {
						// can't remove bitmap from hardCache, so stop creating
						// this
						// bitmap.
						return false;
					}
				}
				// check on mSoftBitmapCache
				SoftReference<Bitmap> softBitmap = mSoftBitmapCache.get(path);
				if (softBitmap != null) {
					// move it to mHardBitmapCache
					mSoftBitmapCache.remove(path);
					if (softBitmap != null && softBitmap.get() != null) {
						mHardBitmapCache.put(path, softBitmap.get());
						softBitmap = null;
						return true;
					}
				}

				Log.d("CacheManager", "path = " + path );
				bitmap = BitmapFactory.decodeFile(path);
				Log.d("CacheManager", "bitmap = " + bitmap );
				// Cache this bitmap.
				mHardBitmapCache.put(path, bitmap);
			}

		} catch (OutOfMemoryError e) {
			e.printStackTrace();
		}
		// decode
		return true;
	}

	/**
	 * Remove a bitmap not use.
	 * 
	 * @return true: if a bitmap has been deleted successful. Otherwise return
	 *         false.
	 */
	private boolean removeABitmapFromCache() {
		boolean result = false;
		if (mItems == null) {
			return result;
		}
		String keyRemove = null;
		synchronized (mItems) {
			int numberItems = mItems.size();
			int maxDistance = Math.max(mFirstVisiblePos, numberItems
					- mLastVisiblePos);

			for (int i = maxDistance; i > 0; i--) {
				int pos = mFirstVisiblePos - i;
				if (pos >= 0) {
					String path = mItems.get(pos).getImage();
					if (mHardBitmapCache.containsKey(path)) {
						keyRemove = path;
						break;
					}
				}
				pos = mLastVisiblePos + i;
				if (pos < numberItems) {
					String path = mItems.get(pos).getImage();
					if (mHardBitmapCache.containsKey(path)) {
						keyRemove = path;
						break;
					}
				}
			}
		}

		if (!TextUtils.isEmpty(keyRemove)
				&& mHardBitmapCache.containsKey(keyRemove)) {
			// move it to softCache.
			Bitmap bitmap = mHardBitmapCache.get(keyRemove);
			mHardBitmapCache.remove(keyRemove);
			if (bitmap != null) {

				mSoftBitmapCache.put(keyRemove, new SoftReference<Bitmap>(
						bitmap));
			}
			result = true;
		}
		return result;
	}

	/**
	 * Move all bitmaps from HashCache to SoftCache. This method is called when
	 * pause the activity.
	 */
	public void removeAllBitmapsFromCache() {
		if (mHardBitmapCache == null) {
			return;
		}
		synchronized (mHardBitmapCache) {
			// Only recycle bitmaps that not in visible area.
			Set<Entry<String, Bitmap>> set = mHardBitmapCache.entrySet();
			for (Entry<String, Bitmap> entry : set) {
				String key = entry.getKey();
				// mHardBitmapCache.remove(key);
				Bitmap bitmap = entry.getValue();
				// move this bitmap to mSoftBitmapCache
				mSoftBitmapCache.put(key, new SoftReference<Bitmap>(bitmap));
			}
			mHardBitmapCache.clear();
		}
		LogUtils.logError(TAG, "[removeAllBitmapsFromCache]- HarCache size = "
				+ mHardBitmapCache.size() + "SoftCache size = "
				+ mSoftBitmapCache.size());
	}

	/**
	 * Removes all bitmaps from hardCache and softCache. This method is called
	 * when finish the activity.
	 */
	public void releaseMemory() {
		mIsStop = true;
		if (mHardBitmapCache == null) {
			return;
		}
		synchronized (mHardBitmapCache) {
			// Only recycle bitmaps that not in visible area.
			Set<Entry<String, Bitmap>> set = mHardBitmapCache.entrySet();
			if (set != null && set.size() > 0) {
				for (Entry<String, Bitmap> entry : set) {
					// mHardBitmapCache.remove(entry.getKey());
					Bitmap bitmap = entry.getValue();
					if (bitmap != null) {
						bitmap.recycle();
						bitmap = null;
					}
				}
			}
			mHardBitmapCache.clear();

			if (mSoftBitmapCache != null) {
				// Recycle all bitmaps and clear softReference
				Set<Entry<String, SoftReference<Bitmap>>> softSet = mSoftBitmapCache
						.entrySet();
				if (softSet != null && softSet.size() > 0) {
					for (Entry<String, SoftReference<Bitmap>> entry : softSet) {
						SoftReference<Bitmap> bitmapRef = entry.getValue();
						if (bitmapRef != null) {
							Bitmap bitmap = bitmapRef.get();
							if (bitmap != null) {
								bitmap.recycle();
								bitmap = null;
							}
						}
					}
				}
				mSoftBitmapCache.clear();
				mSoftBitmapCache = null;
			}

		}
		mHardBitmapCache = null;
	}

	public void reset() {
		removeAllBitmapsFromCache();
		if (mIsPausing) {
			mIsPausing = false;
		}

	}

	public int getResolutionDecode() {
		return mResolutionDecode;
	}

	public void setResolutionDecode(int mResolutionDecode) {
		this.mResolutionDecode = mResolutionDecode;
	}
}
